<template>
  <view :id="rateBoxId" class="mxp-rate" :style="cusStyle" :class="cusClass">
    <view class="content" @touchmove.stop.passive="touchMove" @touchend.stop="touchEnd">
      <view v-for="(_item, index) in Number(count)" :id="`${rateId}-${index}`" :key="index" class="item">
        <view :id="rateId" class="icon-wrap" @click.stop.prevent="clickHandler">
          <mxp-icon
            :name="Math.floor(data.activeIndex) > index ? activeIcon : inactiveIcon"
            :color="Math.floor(data.activeIndex) > index ? (disabled ? disabledActiveColor : activeColor) : inactiveColor"
            :cusStyle="{ paddingLeft: `calc(${gutter} / 2)`, paddingRight: `calc(${gutter} / 2)` }"
            :size="size"
          ></mxp-icon>
        </view>
        <view v-if="halfStar" class="half icon-wrap" :style="[{ width: `${data.rateWidth / 2}px` }]" @click.stop.prevent="clickHandler">
          <mxp-icon
            :name="Math.ceil(data.activeIndex) > index ? activeIcon : inactiveIcon"
            :color="Math.ceil(data.activeIndex) > index ? (disabled ? disabledActiveColor : activeColor) : inactiveColor"
            :cusStyle="{ paddingLeft: `calc(${gutter} / 2)`, paddingRight: `calc(${gutter} / 2)` }"
            :size="size"
          ></mxp-icon>
        </view>
      </view>
    </view>
  </view>
</template>

<script lang="ts" setup>
  import { type PropType, computed, getCurrentInstance, onBeforeMount, onMounted, reactive, watch } from 'vue';
  import type { $AllColorStr, $SizeUnitStr } from '@/mxp-ui/mxp-tool/types/interface';
  import MxpIcon from '@/mxp-ui/mxp-icon/mxp-icon.vue';
  import type { MxpIconFont } from '../mxp-icon/mxp-icon-font-map';
  import { publicStyeProps } from '../mxp-tool/utils/public-props';
  import { getOnlyID } from '../mxp-tool/utils/common';
  import { getDomSize } from '../mxp-tool/utils/dom';
  import { lightenDarkenColorStorage } from '../mxp-tool/utils/color';
  import type { TouchEvent } from 'mxp-uni-types';
  import mxpGetSysInfoStore from '../mxp-tool/store/mxp-store-sys-info';

  defineOptions({
    name: 'mxp-rate',
  });

  const props = defineProps({
    /** 绑定的值 */
    modelValue: { type: Number, default: 0 },
    /** 要显示的星星数量 */
    count: { type: Number, default: 5 },
    /** 是否不可选中 */
    disabled: { type: Boolean, default: false },
    /** 是否只读 */
    readonly: { type: Boolean, default: false },
    /** 星星的大小 */
    size: { type: String as PropType<$SizeUnitStr>, default: '18px' },
    /** 未选中时的颜色 */
    inactiveColor: { type: String as PropType<$AllColorStr>, default: '#b2b2b2' },
    /** 选中的颜色 */
    activeColor: { type: String as PropType<$AllColorStr>, default: '#223aec' },
    /** 星星之间的间距 */
    gutter: { type: String as PropType<$SizeUnitStr>, default: '6px' },
    /** 最少能选择的星星个数 */
    minCount: { type: Number, default: 1 },
    /** 是否允许半星 */
    halfStar: { type: Boolean, default: false },
    /** 选中时的图标(星星) */
    activeIcon: { type: String as PropType<MxpIconFont>, default: 'star_fill' },
    /** 未选中时的图标(星星) */
    inactiveIcon: { type: String as PropType<MxpIconFont>, default: 'star' },
    /** 是否可以通过滑动手势选择评分 */
    touchable: { type: Boolean, default: true },
    ...publicStyeProps,
  });

  const emits = defineEmits<{
    (e: 'update:modelValue', val: number): void;
    (e: 'change', val: number): void;
  }>();

  const rateBoxId = getOnlyID('mxp-rate');
  const rateId = getOnlyID('rate');
  const sys = mxpGetSysInfoStore().getSysInfo();
  const instance = getCurrentInstance();
  const data = reactive({
    /** 评分盒子左边到屏幕左边的距离，用于滑动选择时计算距离 */
    rateBoxLeft: 0,
    /** 滑动时，点击的星星的索引 */
    activeIndex: 0,
    /** 每个星星的宽度 */
    rateWidth: 0,
    /** 标识是否正在滑动，由于iOS事件上touch比click先触发，导致快速滑动结束后，接着触发click，导致事件混乱而出错 */
    moving: false,
  });

  watch(
    () => props.modelValue,
    (val) => {
      data.activeIndex = val;
    }
  );

  const disabledActiveColor = computed<$AllColorStr>(() => lightenDarkenColorStorage(props.activeColor, 0.7) as $AllColorStr);

  onBeforeMount(() => {
    data.activeIndex = Number(props.modelValue);
  });

  onMounted(() => {
    init();
  });

  /** 初始化 */
  function init() {
    setTimeout(() => {
      getDomSize(`#${rateBoxId}`, instance).then((res) => {
        data.rateBoxLeft = res.left!;
      });
      getDomSize(`#${rateId}-0`, instance).then((res) => {
        data.rateWidth = res.width!;
      });
    }, 10);
  }

  /** 手指滑动 */
  function touchMove(e: TouchEvent) {
    // 如果禁止通过手动滑动选择，返回
    if (!props.touchable) {
      return;
    }
    const x = (e.changedTouches && e.changedTouches[0]!.pageX) || (e.detail && e.detail.pageX);
    getActiveIndex(x);
  }

  /** 停止滑动 */
  function touchEnd(e: TouchEvent) {
    // 如果禁止通过手动滑动选择，返回
    if (!props.touchable) {
      return;
    }

    const x = (e.changedTouches && e.changedTouches[0]!.pageX) || (e.detail && e.detail.pageX);
    getActiveIndex(x);
  }

  /** 通过点击，直接选中 */
  function clickHandler(e: TouchEvent) {
    // ios上，moving状态取消事件触发
    if (sys.value.platform === 'ios' && data.moving) {
      return;
    }
    let x = 0;
    // 点击时，在nvue上，无法获得点击的坐标，所以无法实现点击半星选择
    x = (e.changedTouches && e.changedTouches[0]!.pageX) || e.detail?.pageX || 0;
    getActiveIndex(x, true);
  }

  /** 发出事件 */
  function changeEvent() {
    emits('change', data.activeIndex);
    emits('update:modelValue', data.activeIndex);
  }

  //
  /** 获取当前激活的评分图标 */
  function getActiveIndex(_x: number, isClick = false) {
    if (props.disabled || props.readonly) {
      return;
    }
    // 判断当前操作的点的x坐标值，是否在允许的边界范围内
    const allRateWidth = data.rateWidth * props.count + data.rateBoxLeft;
    // 如果小于第一个图标的左边界，设置为最小值，如果大于所有图标的宽度，则设置为最大值
    const x = Math.max(data.rateBoxLeft, Math.min(allRateWidth, Number(_x))) - data.rateBoxLeft;
    // 滑动点相对于评分盒子左边的距离
    const distance = x;
    // 滑动的距离，相当于多少颗星星
    let index = 0;
    // 判断是否允许半星
    if (props.halfStar) {
      index = Math.floor(distance / data.rateWidth);
      // 取余，判断小数的区间范围
      const decimal = distance % data.rateWidth;
      if (decimal <= data.rateWidth / 2 && decimal > 0) {
        index += 0.5;
      } else if (decimal > data.rateWidth / 2) {
        index++;
      }
    } else {
      index = Math.floor(distance / data.rateWidth);
      // 取余，判断小数的区间范围
      const decimal = distance % data.rateWidth;
      // 非半星时，只有超过了图标的一半距离，才认为是选择了这颗星
      if (isClick) {
        if (decimal > 0) {
          index++;
        }
      } else {
        if (decimal > data.rateWidth / 2) {
          index++;
        }
      }
    }
    data.activeIndex = Math.min(index, props.count);
    // 对最少颗星星的限制
    if (data.activeIndex < props.minCount) {
      data.activeIndex = props.minCount;
    }
    changeEvent();
    // 设置延时为了让click事件在touchmove之前触发
    setTimeout(() => {
      data.moving = true;
    }, 10);
    // 一定时间后，取消标识为移动中状态，是为了让click事件无效
    setTimeout(() => {
      data.moving = false;
    }, 10);
  }
</script>

<style lang="scss" scoped>
  .mxp-rate {
    display: flex;
    align-items: center;
    padding: 0;
    margin: 0;
    touch-action: none;

    .content {
      display: flex;

      .item {
        position: relative;

        .icon-wrap.half {
          position: absolute;
          top: 0;
          left: 0;
          overflow: hidden;
        }
      }
    }
  }
</style>
